﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Logic.ClarizenSvc;
using System.ComponentModel;
using Logic;
using BLL;
using Logic.ClarizenObjects;
using log4net;
using System.Web.Caching;
using System.Web;

namespace Logic
{
    public class QueryComponent
    {
        static ILog logger = LogManager.GetLogger("default");

        public QueryComponent(LoginComponent service)
        {
            this.Service = service;
        }

        public QueryComponent()
        {
            LoginComponent login = new LoginComponent();
            login.Login(Users.GetClarizenSession());

            this.Service = login;
        }

        public QueryComponent(String session)
        {
            LoginComponent login = new LoginComponent();
            login.Login(session);

            this.Service = login;
        }

        private LoginComponent Service;

        public List<GenericEntity> GetListByTypeName(String typeName, String search = "", int? count = null, String[] fields = null, Boolean useCache = false, int page = 0)
        {
            Boolean hasMore;
            long totalCount;
            return GetListByTypeName(typeName: typeName, hasMore: out hasMore, search:search, count: count, fields: fields,  useCache: useCache, page: page, totalCount:out totalCount);
        }

        public List<GenericEntity> GetListByTypeName(String typeName, out Boolean hasMore, out long totalCount, String search = "", int? count = null, String[] fields = null, Boolean useCache = false, int page = 0, Boolean getTotalCount = false, Condition conditions = null)
        {
            totalCount = 0;
            hasMore = false;
            if (fields == null)
                fields = new String[] { "Name" };
            if (useCache && HttpContext.Current.Cache[typeName] != null)
                return (List<GenericEntity>)HttpContext.Current.Cache[typeName];
            
            EntityQuery qry = new EntityQuery { TypeName = typeName, Fields = fields, Paging = new Paging { PageSize = count.HasValue && count.Value < 200 ? count.Value : 200, PageNumber = page, PageNumberSpecified =true, PageSizeSpecified=true } };
            if (fields.Contains("Name"))
                qry.Orders = new OrderBy[] {new OrderBy{FieldName="Name", Order=Order.Ascending, OrderSpecified=true} };
            else if(fields.Length > 0)
                qry.Orders = new OrderBy[] { new OrderBy { FieldName = fields[0], Order = Order.Ascending, OrderSpecified = true } };


            List<Condition> con = new List<Condition>();
            if (conditions != null)
                con.Add(conditions);

            if (search != null)
            {
                Compare condition = new Compare { LeftExpression = new FieldExpression { FieldName = "Name" }, Operator = Operator.Contains, RightExpression = new ConstantExpression { Value = search } };
                con.Add(condition);
            }

            qry.Where = new And {Conditions = con.ToArray() };

            if (getTotalCount)
            {
                try
                {
                    var cq = new CountQuery { Query = qry };
                    totalCount = ((CountQueryResult)Execute(cq)[0]).Count;
                }
                catch { }
            }

            var queryRes = Service.Service.Query(qry);
            ClarizenUtils.CheckResponseStatus(queryRes);
            List<BaseEntity> res = new List<BaseEntity>();
            res.AddRange(queryRes.Entities);
            hasMore = queryRes.Paging.HasMore;

            while (queryRes.Paging.HasMore && (!count.HasValue || count > res.Count))
            {
                qry.Paging = queryRes.Paging;
                queryRes = Service.Service.Query(qry);
                ClarizenUtils.CheckResponseStatus(queryRes);
                res.AddRange(queryRes.Entities);
                hasMore = queryRes.Paging.HasMore;
            }

            var retValue = res.Cast<GenericEntity>().ToList();
            HttpContext.Current.Cache[typeName] = retValue;
            return retValue;
        }

        public Dictionary<String, List<GenericEntity>> GetListByTypeName(List<ListQuery> queriesReps)
        {
            List<EntityQuery> queries = queriesReps.Select(q => new EntityQuery
            {
                TypeName = q.Type,
                Fields = new[] { "Name" },
                Paging = new Paging { PageSize = q.Batch.HasValue ? q.Batch.Value : q.Count.HasValue && q.Count.Value < 200 ? q.Count.Value : 200 },
                Where = q.Search == null? null: new Compare { LeftExpression = new FieldExpression { FieldName = "Name" }, Operator = Operator.Contains, RightExpression = new ConstantExpression { Value = q.Search } }
            }).ToList();


            return ExecuteQueries(queries.Cast<Query>().ToList());
        }


        public List<GenericEntity> ExecuteQuery(Query query)
        {
            var res = ExecuteQueries(new List<Query> { query });
            return res.ElementAt(0).Value;
        }

        public Dictionary<String, List<GenericEntity>> ExecuteQueries(List<Query> queries)
        {
            logger.Debug("making queries with " + Service.Service.Session.ID);
            Dictionary<String, List<GenericEntity>> res = new Dictionary<String, List<GenericEntity>>();

            while (queries.Count > 0)
            {
                var queryRes = Service.Service.Execute(queries.ToArray()).Cast<QueryResult>().ToList();
                ClarizenUtils.CheckResponseStatus(queryRes.ToArray());

                for (int i = 0; i < queryRes.Count; i++)
                {
                    var TypeName = queries[i] is EntityQuery ? (queries[i] as EntityQuery).TypeName :
                                queries[i].GetType().Name;

                    if (!res.ContainsKey(TypeName))
                        res.Add(TypeName, new List<GenericEntity>(queryRes[i].Entities.Cast<GenericEntity>()));
                    else res[TypeName].AddRange(new List<GenericEntity>(queryRes[i].Entities.Cast<GenericEntity>()));

                    queries[i].Paging = queryRes[i].Paging;
                    if (!queryRes[i].Paging.HasMore || (queries[i].Count.HasValue && queries[i].Count.Value <= res[TypeName].Count))
                    {
                        queries.RemoveAt(i);
                        queryRes.RemoveAt(i);
                        i--;
                    }
                }
            }

            return res;
        }

        public EntityId CreateEntity(BaseEntity entity)
        {
            CreateMessage msg = new CreateMessage { Entity = entity };

            var res = Service.Service.Execute(new BaseMessage[] { msg });
            ClarizenUtils.CheckResponseStatus(res);

            var cRes = res.Length > 0 ? res[0] as CreateResult : null;
            if (cRes.Id != null)
                return cRes.Id;
            else return null;
        }

        public Result[] Execute(params BaseMessage[] messages)
        {
            var res = Service.Service.Execute(messages);
            ClarizenUtils.CheckResponseStatus(res);

            return res;
        }

        public void UpdateEntity(BaseEntity entity)
        {
            UpdateMessage msg = new UpdateMessage { Entity = entity };

            var res = Service.Service.Execute(new BaseMessage[] { msg });
            ClarizenUtils.CheckResponseStatus(res);
        }

        public void DeleteEntity(EntityId id)
        {
            DeleteMessage msg = new DeleteMessage { Id = id };

            var res = Service.Service.Execute(new BaseMessage[] { msg });
            ClarizenUtils.CheckResponseStatus(res);
        }

        public GenericEntity RetrieveEntity(EntityId id, String[] fields = null, Boolean withPermissions = false)
        {
            if (fields == null)
                fields = new string[] { "Name" };
            var query = new RetrieveMessage
            {
                Fields = fields,
                Id = id
            };

            if (withPermissions)
            {
                query.Permissions = PermissionOptions.ObjectAndFields;
                query.PermissionsSpecified = true;
            }

            var res = Execute(new BaseMessage[] { query });

            if (res.Length > 0)
                return (GenericEntity)((RetrieveResult)res[0]).Entity;

            return null;
        }

        public class ListQuery
        {
            public String Type;
            public int? Count;
            public int? Batch;
            public String Search;
        }

        public List<WorkItem> GetWorkItems(WorkItemType ItemType, WorkItemFilter WhereRole, WorkItemState State, String filter = "", string ParentID = "", String ParentType = "", bool UsePaging = true, int PageNumber = 0)
        {
            Boolean hasMore;
            long count = 1;
            Exception err;
            return GetWorkItems(ItemType, WhereRole, State, out hasMore, out count, out err, filter, ParentID, ParentType, UsePaging, PageNumber);
        }

        public List<WorkItem> GetWorkItems(WorkItemType ItemType, WorkItemFilter WhereRole, WorkItemState State, out Boolean hasMore, out long count, out Exception error, String filter = "", string ParentID = "", String ParentType = "", bool UsePaging = true, int PageNumber = 0, Boolean returnCount = false, string[] fields = null, int fetchCount = 30, Order order = Order.Ascending, String orderField = "Name")
        {
            error = null;
            if (fields == null)
                fields = BaseObject.GetFields(typeof(WorkItem));
            count = -1;
            WorkItemsQuery wiQuery = new WorkItemsQuery();
            wiQuery.ItemsType = ItemType;
            wiQuery.ItemsTypeSpecified = true;
            wiQuery.ItemsFilter = WhereRole;
            wiQuery.ItemsFilterSpecified = true;
            wiQuery.ItemsState = State;
            wiQuery.ItemsStateSpecified = true;
            wiQuery.Fields = fields;
            wiQuery.Paging = new Paging() { PageSize = fetchCount, PageSizeSpecified = UsePaging, PageNumber = PageNumber, PageNumberSpecified = true };
            wiQuery.Count = fetchCount;
            wiQuery.Orders = new OrderBy[] { new OrderBy { FieldName = orderField, OrderSpecified = true, Order = order } };
            var and = new And();
            var conditions = new List<Condition>();
            wiQuery.Where = and;
            if (ParentID != "")
            {
                Compare condition = new Compare();
                condition.LeftExpression = new FieldExpression() { FieldName = "Parent" };
                condition.Operator = Operator.Equal;
                if(ParentID != null)
                    condition.RightExpression = new ConstantExpression()
                    {
                        Value = new EntityId { TypeName = ParentType, Value = ParentID }
                    };
                else
                    condition.RightExpression = new ConstantExpression()
                    {
                        Value = null
                    };
                conditions.Add(condition);
            }

            if (!String.IsNullOrEmpty(filter))
            {
                Compare condition = new Compare();
                condition.LeftExpression = new FieldExpression() { FieldName = "Name" };
                condition.Operator = Operator.Contains;
                condition.RightExpression = new ConstantExpression()
                {
                    Value = filter
                };

                conditions.Add(condition);
            }

            and.Conditions = conditions.ToArray();
            
            try
            {
                List<BaseMessage> mesages = new List<BaseMessage>();
                mesages.Add(wiQuery);
                if (returnCount)
                {
                    var cq = new CountQuery { Query = wiQuery };
                    mesages.Add(cq);
                }
                var msgRes = Execute(mesages.ToArray());

                var qRes = (QueryResult)msgRes[0];
                var res = qRes.Entities.Select(en => new WorkItem((GenericEntity)en)).ToList();
                if (returnCount)
                    count = ((CountQueryResult)msgRes[1]).Count;
                hasMore = qRes.Paging.HasMore;
                return res;
            }
            catch(Exception e)
            {
                hasMore = true;
                error = e; 
                return null;
            }
        }

        public QueryResult TestGetWorkItems()
        {
            WorkItemsQuery wiQuery = new WorkItemsQuery();
            wiQuery.ItemsFilter = WorkItemFilter.IManageTheProject;
            wiQuery.ItemsFilterSpecified = true;

            wiQuery.ItemsType = WorkItemType.Task;
            wiQuery.ItemsTypeSpecified = true;

            List<BaseMessage> mesages = new List<BaseMessage>();
            mesages.Add(wiQuery);

            var res = Service.Service.Execute(mesages.ToArray());
            ClarizenUtils.CheckResponseStatus(res);

            var qRes = (QueryResult)res[0];

            return qRes;
        }

        public EntityDescription GetEntityDescription(string type)
        {
            DescribeEntitiesMessage describeEntity = new DescribeEntitiesMessage();
            describeEntity.TypeNames = new String[] { type };
            var entitiesDetails = (DescribeEntitiesResult)Service.Service.Metadata(describeEntity);
            ClarizenUtils.CheckResponseStatus(entitiesDetails);
            if (entitiesDetails.EntityDescriptions.Length > 0)
                return entitiesDetails.EntityDescriptions[0];
            else return null;
        }

        public Boolean IsLoggedIn
        {
            get { return Service.IsLoggedIn; }
        }

        public User GetCurrentUser(BO.User localUser)
        {
            if (string.IsNullOrEmpty(localUser.ClarizenUserID))
            {
                var res = (GetSessionInfoResult)Execute(new GetSessionInfoMessage())[0]; 
                localUser.ClarizenUserID = res.UserId;
            }

            if (!string.IsNullOrEmpty(localUser.ClarizenUserID))
            {
                var user = new User(RetrieveEntity(new EntityId { TypeName = "User", Value = localUser.ClarizenUserID }, new string[]{"Name", "DisplayName"}));
                localUser.UserName = user.Name;
                localUser.Update();
                return user;
            }
            return null;
        }

        public Boolean RefershCall()
        {
            var res = (GetSessionInfoResult)Execute(new GetSessionInfoMessage())[0];
            return res.Success && res.SessionId == Users.GetClarizenSession();
        }
    }
}
